/////////////////////////////////////////////////////////////////////////
//
// Amuse Engine SDK - core/memory
// Copyright( c) 2013.  All Rights Reserved
//
// File:		AETaskManager.cpp
// Author:		Gianluca Belardelli
// Date:		05/04/2014
//
/////////////////////////////////////////////////////////////////////////
#include <AEEngine.h>

void AETaskManager::Init( AEUINT32 uiNumOfThread )
{
    m_uiActiveSystemTasksInQueue = 0;
    m_lpThreadPool = new AEThreadPool();

    AEASSERT( m_lpThreadPool != NULL );

    AEUINT32 uiNumberOfThreads = uiNumOfThread;
	if( uiNumberOfThreads == 0 ) 
        uiNumberOfThreads = AEProcessor::GetInstance().GetNumCPUAvailableForProcess();

    m_lpThreadPool->Initialize( uiNumberOfThreads - 1 );

	// Cache the thread count for display.
	AEEngineStats::GetInstance().SetActiveThreadCount( uiNumberOfThreads );

    for( AEUINT32 i=0; i < AE_MAX_TASKS_ALLOWED; i++ )
        m_lpevSystemTaskEvents[i] = new AEEvent( FALSE, FALSE );//CreateEvent( NULL, False, False, NULL );
}
	
void AETaskManager::Shutdown( void )
{
	delete m_lpThreadPool;

    for( AEUINT32 i=0; i < AE_MAX_TASKS_ALLOWED; i++ )
        delete m_lpevSystemTaskEvents[i];
}

void AETaskManager::IssueJobsForSystemTasks( AESystemTaskI **lpTasks, AEUINT32 uiCount, AEFLOAT32 fDeltaTime )
{
    m_uiActiveSystemTasksInQueue = uiCount;

    for ( AEUINT32 i = 0; i < uiCount; i++ )
    {
		AEUINT32 uiTaskQ = AEEngineSystem::GetSystemIndex( lpTasks[ i ]->GetSystemType() );

		AEHANDLE hHandle = m_lpevSystemTaskEvents[ uiTaskQ ]->GetHandle();

        m_sysTaskQueue[ uiTaskQ ].m_hWorkCompleted				= m_lpevSystemTaskEvents[ uiTaskQ ];
        m_sysTaskQueue[ uiTaskQ ].m_lpSystemTask				= lpTasks[ i ];
        m_sysTaskQueue[ uiTaskQ ].m_fDeltaTime					= fDeltaTime;
        m_sysTaskQueue[ uiTaskQ ].m_lpfnJobCompletionFunction	= NULL;
		m_sysTaskQueue[ uiTaskQ ].m_lpCompletionUserData		= NULL;

        m_lpThreadPool->EnqueueWorkItem( &m_sysTaskQueue[ uiTaskQ ] );

        m_hActiveSystemTasksInQueue[ i ] = m_lpevSystemTaskEvents[ uiTaskQ ]->GetHandle();
	}
}
	
void AETaskManager::WaitForSystemTasks( AESystemTaskI **lpTasks, AEUINT32 uiCount )
{
}

void AETaskManager::WaitForAllSystemTasks( void )
{
}

void AETaskManager::NonStandardPerThreadCallback( AEJobFunction lpfnCallback, void *lpData )
{
	m_lpThreadPool->PrimaryThreadExecute( lpfnCallback, lpData );

	// call it for ourself, too
	lpfnCallback( lpData );
}

AEUINT32 AETaskManager::GetRecommendedJobCount( AEITaskManager::AEJobCountInstructionHints eHints )
{
    //
    // Ignoring hints for now and just returning the number of available threads.
    //
    return m_lpThreadPool->GetThreadCount();
}
	
void AETaskManager::SetNumberOfThreads( AEUINT32 uiNumberOfThreads )
{
	m_lpThreadPool->SetActiveThreadCount( uiNumberOfThreads - 1 );
}

AEUINT32 AETaskManager::GetNumberOfThreads( void )
{
	return m_lpThreadPool->GetThreadCount();
}

void AETaskManager::ParallelFor( AESystemTaskI *lpSystemTask, AEParallelForFunction lpfnJobFunction,
					void *lpParam, AEUINT32 uiBegin, AEUINT32 uiEnd, AEUINT32 uiMinGrain )
{
	AEUINT32 uiThreads = ( m_lpThreadPool->GetActiveThreadCount() + 1 );
	AEUINT32 uiCount = ( uiEnd - uiBegin );
	
	AEASSERT( uiThreads > 0 );
	AEASSERT( uiCount >= 0 );
	
	AEUINT32 uiGrainSize = ( uiCount / uiThreads );

	if( uiGrainSize < uiMinGrain )
	{
		uiGrainSize = uiMinGrain;
		AEASSERT( uiGrainSize > 0 );

		uiThreads = ( uiCount / uiGrainSize );
	}

	if( uiThreads > 1 )
	{
		// this job count will be used to track completion of jobs put into the pool
		// using InterlockedDecrementRelease
		volatile AEUINT32 uiJobWaitCount = ( uiThreads - 1 );

		AEUINT32 uiStart = 0;
		AEUINT32 uiFinish = 0;
		
		AESysTask *lpParentTask = NULL;

		if( lpSystemTask != NULL )
		{
			AEUINT32 iTaskQ = AEEngineSystem::GetSystemIndex( lpSystemTask->GetSystemType() );
			lpParentTask = &m_sysTaskQueue[ iTaskQ ];
		}
			
		// dispatch other jobs
		for( AEUINT32 t = 1; t < uiThreads; t++ )
		{
			uiFinish= ( uiStart + uiGrainSize );

			// this will get deallocated after the wrapped is marked Completed
			// TODO: Fare un pool per togliere la new
			AESysParallelForTask *lpWrapper = new AESysParallelForTask();
			AEASSERT( lpWrapper != NULL );
			lpWrapper->m_lpParentTask = lpParentTask;
			lpWrapper->m_lpfnParallelForFunction = lpfnJobFunction;
			lpWrapper->m_lpUserData = lpParam;
			lpWrapper->m_uiStart = uiStart;
			lpWrapper->m_uiEnd = uiFinish;
			lpWrapper->m_nJobWaitCount = &uiJobWaitCount;
			
			m_lpThreadPool->EnqueueWorkItem( lpWrapper );
			
			uiStart = uiFinish;
		}

		// now do our job
		uiFinish = uiCount;
		
		// Call the function, and figure out how long it took.
		AETimer tTimer(FALSE);
		AEUINT64 counter;
		tTimer.StartHighResTimer();
		lpfnJobFunction( lpParam, uiStart, uiEnd );
		counter = tTimer.StopHighResTimer();

		// Log this job's time in instrumentation
		if( lpSystemTask != NULL )
		{
			//Singletons::Instrumentation.CaptureJobCounterTicks( lpSystemTask->GetSystemType(), counter );
			AEEngineStats::GetInstance().CaptureJobCounterTicks( lpSystemTask->GetSystemType(), counter );
		}
	
		// now check up on progress of the dispatched jobs
		while( uiJobWaitCount > 0 )
		{
			// pull something off of the job queue and execute it
			m_lpThreadPool->PopAndProcessWorkItem();
		}
	}
	else
	{
		// just do it ourselves
		lpfnJobFunction( lpParam, uiBegin, uiEnd );
	}
}



